/* 
 * File:   matrix_operations.h
 * Author: gustavo
 *
 * Created on June 9, 2012, 4:48 AM
 */

#ifndef MATRIX_OPERATIONS_H
#define	MATRIX_OPERATIONS_H

#include "matrix.hpp"
#include "operator.hpp"

namespace imp {

	namespace math {

		/* = Operator <op> Operator ============================================= */
		template <typename T, class Opl, class Opr> const Operator<T, Addition> operator +(const Operator<T, Opl>& opl, const Operator<T, Opr> opr) {

			Matrix<T> Ml(opl.rows(), opl.columns());
			opl.eval(Ml);

			Matrix<T> Mr(opr.rows(), opr.columns());
			opr.eval(Mr);

			Operator<T, Addition> op(Ml, Mr);
			return op;
		}

		template <typename T, class Opl, class Opr> const Operator<T, Subtraction> operator -(const Operator<T, Opl>& opl, const Operator<T, Opr> opr) {

			Matrix<T> Ml(opl.rows(), opl.columns());
			opl.eval(Ml);

			Matrix<T> Mr(opr.rows(), opr.columns());
			opr.eval(Mr);

			Operator<T, Subtraction> op(Ml, Mr);
			return op;
		}

		//	template <typename T, class Opl, class Opr> const Operator<T, Scale> operator /(const Operator<T, Opl> opl, const Operator<T, Opr> opr) {
		//
		//		if(opr.rows() != 1 || opr.columns() != 1 ) {
		//			__error("Ooppps\n");
		//		}
		//		
		//		Matrix<T> Ml(opl.rows(), opl.columns());
		//		opl.eval(Ml);
		//
		//		Matrix<T> Mr(opr.rows(), opr.columns());
		//		opr.eval(Mr);
		//
		//		Operator<T, Scale> op(Ml, (1.0 / Mr.data[0]));
		//		return op;
		//	}

		/* = Matrix Increment =================================================== */
		template <typename T> const Operator<T, Increment> operator +(const Matrix<T>& M, T inc) {
			Operator<T, Increment> op(M, inc);
			return op;
		}

		template <typename T> const Operator<T, Increment> operator +(T inc, const Matrix<T>& M) {
			Operator<T, Increment> op(M, inc);
			return op;
		}

		template <typename T, class Op> const Operator<T, Increment> operator +(const Operator<T, Op> opi, T inc) {

			Matrix<T> M(opi.rows(), opi.columns());
			opi.eval(M);

			Operator<T, Increment> op(M, inc);
			return op;
		}

		template <typename T, class Op> const Operator<T, Increment> operator +(T inc, const Operator<T, Op> opi) {

			Matrix<T> M(opi.rows(), opi.columns());
			opi.eval(M);

			Operator<T, Increment> op(M, inc);
			return op;
		}

		/* = Matrix Decrement =================================================== */
		template <typename T> const Operator<T, Decrement> operator -(const Matrix<T>& M, T dec) {
			Operator<T, Decrement> op(M, dec);
			return op;
		}

		template <typename T> const Operator<T, Decrement> operator -(T dec, const Matrix<T>& M) {
			Operator<T, Decrement> op(dec, M);
			return op;
		}

		template <typename T, class Op> const Operator<T, Decrement> operator -(const Operator<T, Op> opi, T dec) {

			Matrix<T> M(opi.rows(), opi.columns());
			opi.eval(M);

			Operator<T, Decrement> op(M, dec);
			return op;
		}

		template <typename T, class Op> const Operator<T, Decrement> operator -(T dec, const Operator<T, Op> opi) {

			Matrix<T> M(opi.rows(), opi.columns());
			opi.eval(M);

			Operator<T, Decrement> op(dec, M);
			return op;
		}

		/* = Matrix Addition ==================================================== */
		template <typename T> const Operator<T, Addition> operator +(const Matrix<T>& Ml, const Matrix<T>& Mr) {
			Operator<T, Addition> op(Ml, Mr);
			return op;
		}

		template <typename T, class Op> const Operator<T, Addition> operator +(const Operator<T, Op> opi, const Matrix<T>& Mr) {

			Matrix<T> M(opi.rows(), opi.columns());
			opi.eval(M);

			Operator<T, Addition> op(M, Mr);
			return op;
		}

		template <typename T, class Op> const Operator<T, Addition> operator +(const Matrix<T>& Ml, const Operator<T, Op> opi) {

			Matrix<T> M(opi.rows(), opi.columns());
			opi.eval(M);

			Operator<T, Addition> op(Ml, M);
			return op;
		}

		/* = Matrix Subtraction ================================================= */
		template <typename T> const Operator<T, Subtraction> operator -(const Matrix<T>& Ml, const Matrix<T>& Mr) {
			Operator<T, Subtraction> op(Ml, Mr);
			return op;
		}

		template <typename T, class Op> const Operator<T, Subtraction> operator -(const Operator<T, Op> opi, const Matrix<T>& Mr) {

			Matrix<T> M(opi.rows(), opi.columns());
			opi.eval(M);

			Operator<T, Subtraction> op(M, Mr);
			return op;
		}

		template <typename T, class Op> const Operator<T, Subtraction> operator -(const Matrix<T>& Ml, const Operator<T, Op> opi) {

			Matrix<T> M(opi.rows(), opi.columns());
			opi.eval(M);

			Operator<T, Subtraction> op(Ml, M);
			return op;
		}

		/* = Matrix Scale ======================================================= */
		template <typename T> const Operator<T, Scale> operator *(const Matrix<T>& M, T s) {
			Operator<T, Scale> op(M, s);
			return op;
		}

		template <typename T> const Operator<T, Scale> operator *(T s, const Matrix<T>& M) {
			Operator<T, Scale> op(M, s);
			return op;
		}

		template <typename T, class Op> const Operator<T, Scale> operator *(const Operator<T, Op> opi, T s) {

			Matrix<T> M(opi.rows(), opi.columns());
			opi.eval(M);

			Operator<T, Scale> op(M, s);
			return op;
		}

		template <typename T, class Op> const Operator<T, Scale> operator *(T s, const Operator<T, Op> opi) {

			Matrix<T> M(opi.rows(), opi.columns());
			opi.eval(M);

			Operator<T, Scale> op(M, s);
			return op;
		}

		/* = Matrix Division ==================================================== */
		template <typename T> const Operator<T, Scale> operator /(Matrix<T>& M, T s) {
			Operator<T, Scale> op(M, T(1.0 / s));
			return op;
		}

		template <typename T, class Op> const Operator<T, Scale> operator /(const Operator<T, Op> opi, T s) {

			Matrix<T> M(opi.rows(), opi.columns());
			opi.eval(M);

			Operator<T, Scale> op(M, (1.0 / s));
			return op;
		}

		/* = Matrix Multiplication ========================================== */
		template <typename T> const Operator<T, Multiplication> operator *(const Matrix<T>& Ml, const Matrix<T>& Mr) {
			Operator<T, Multiplication> op(Ml, Mr);
			return op;
		}

		template <typename T, class Op> const Operator<T, Multiplication> operator *(const Operator<T, Op> opi, const Matrix<T>& Mr) {

			Matrix<T> M(opi.rows(), opi.columns());
			opi.eval(M);

			Operator<T, Multiplication> op(M, Mr);
			return op;
		}

		template <typename T, class Op> const Operator<T, Multiplication> operator *(const Matrix<T>& Ml, const Operator<T, Op> opi) {

			Matrix<T> M(opi.rows(), opi.columns());
			opi.eval(M);

			Operator<T, Multiplication> op(Ml, M);
			return op;
		}

	}
}

#endif	/* MATRIX_OPERATIONS_H */

